package github.sf.fw.utils;

import sun.misc.Unsafe;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Objects;

/**
 * Unsafe工具类
 *
 * @author zhoup
 */
public class UnsafeAccess {

    private static final String ANDROID = "THE_ONE";
    private static final String OPEN_JDK = "theUnsafe";
    private static Unsafe unsafe;

    static {
        try {
            unsafe = Unsafe.getUnsafe();
            if (unsafe == null) {
                unsafe = load();
            }
        } catch (Exception e) {
            try {
                unsafe = load();
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }
    }

    public static Unsafe getUnsafe() {
        Objects.requireNonNull(unsafe);
        return unsafe;
    }

    /**
     * Returns the location of a given static field.
     *
     * @param clazz     the class containing the field
     * @param fieldName the name of the field
     * @return the address offset of the field
     */
    public static long objectFieldOffset(Class<?> clazz, String fieldName) {
        try {
            return getUnsafe().objectFieldOffset(clazz.getDeclaredField(fieldName));
        } catch (NoSuchFieldException | SecurityException e) {
            throw new Error(e);
        }
    }

    static Unsafe load() throws NoSuchMethodException,
            InstantiationException, IllegalAccessException, InvocationTargetException {
        Field field;
        try {
            // try OpenJDK field name
            field = Unsafe.class.getDeclaredField(UnsafeAccess.OPEN_JDK);
        } catch (NoSuchFieldException e) {
            try {
                // try Android field name
                field = Unsafe.class.getDeclaredField(UnsafeAccess.ANDROID);
            } catch (NoSuchFieldException e2) {
                // try to create a new instance
                Constructor<Unsafe> unsafeConstructor = Unsafe.class.getDeclaredConstructor();
                unsafeConstructor.setAccessible(true);
                return unsafeConstructor.newInstance();
            }
        }
        field.setAccessible(true);
        return (Unsafe) field.get(null);
    }
}
